home *** CD-ROM | disk | FTP | other *** search
/ Languguage OS 2 / Languguage OS II Version 10-94 (Knowledge Media)(1994).ISO / gnu / recode.lha / recode-3.2.4 / recode.c < prev    next >
C/C++ Source or Header  |  1992-09-29  |  23KB  |  893 lines

  1. /* Conversion of files between different charsets and usages.
  2.    Copyright (C) 1990 Free Software Foundation, Inc.
  3.    Francois Pinard <pinard@iro.umontreal.ca>, 1990.
  4.  
  5.    This program is free software; you can redistribute it and/or modify
  6.    it under the terms of the GNU General Public License as published by
  7.    the Free Software Foundation; either version 2, or (at your option)
  8.    any later version.
  9.  
  10.    This program is distributed in the hope that it will be useful, but
  11.    WITHOUT ANY WARRANTY; without even the implied warranty of
  12.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13.    General Public License for more details.
  14.  
  15.    You should have received a copy of the GNU General Public License
  16.    along with this program; if not, write to the Free Software
  17.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  18. */
  19.  
  20. /* Global declarations and definitions.  */
  21.  
  22. #include <stdio.h>
  23. #include <assert.h>
  24. #include <sys/types.h>
  25. #include <sys/stat.h>
  26. #ifdef USG
  27. #include <string.h>
  28. #else
  29. #include <strings.h>
  30. #define strchr index
  31. #define strrchr rindex
  32. #endif
  33.  
  34. #include "common.h"
  35. #include "steps.h"
  36.  
  37. #ifdef MSDOS
  38.  
  39. #include <dir.h>
  40. #define unlink dummy1
  41. #include <io.h>
  42. #undef unlink
  43. #include <fcntl.h>
  44.  
  45. #endif
  46.  
  47. /* Standard declarations.  */
  48.  
  49. #ifdef STDC_HEADERS
  50. #include <stdlib.h>
  51. #endif
  52.  
  53. /* Some systems do not define EXIT_*, even with STDC_HEADERS.  */
  54. #ifndef EXIT_SUCCESS
  55. #define EXIT_SUCCESS 0
  56. #endif
  57. #ifndef EXIT_FAILURE
  58. #define EXIT_FAILURE 1
  59. #endif
  60.  
  61. #ifdef __STDC__
  62.  
  63. extern int    fstat (int, struct stat *);
  64. extern int    getopt (int, const char **, const char *);
  65. extern int    link (const char *, const char *);
  66. extern void    perror (const char *);
  67. extern int    unlink (const char *);
  68. #ifndef MSDOS
  69. extern int    utime (const char *, time_t[2]);
  70. #endif
  71.  
  72. #endif /* __STDC__ */
  73.  
  74. /* Prototypes specific to the program.  */
  75.  
  76. #ifndef __STDC__
  77. void print_version ();
  78. void print_copyright ();
  79. #else
  80. void print_version (void);
  81. void print_copyright (void);
  82. #endif
  83.  
  84. /* In the `BEFORE:AFTER' parameter, there is a default supplied whenever
  85.    `:AFTER' or `BEFORE:' are used.  */
  86.  
  87. #ifdef MSDOS
  88. #define DEFAULT_CODE "ibmpc"
  89. #else
  90. #define DEFAULT_CODE "latin1"
  91. #endif
  92.  
  93. /* It is expected that no conversion sequence will need more than this
  94.    number of steps.  */
  95.  
  96. #define MAX_CONVERSIONS 10
  97.  
  98. /* Program name.  */
  99.  
  100. const char *program_name;
  101.  
  102. /* If the recoding yields some problems in reversability, the replacement is
  103.    normally not completed and the file is left unrecoded.  The following
  104.    option forces the replacement even if the case the recoding is not
  105.    reversible.  But if recode is used as a mere filter, there is no file
  106.    replacement and this option is then irrelevant.  */
  107.  
  108. int force_option = 0;
  109.  
  110. /* By selecting the following option, the program will echo to stderr the
  111.    sequence of elementary recoding steps which will be taken to effect
  112.    the requested recoding.  */
  113.  
  114. int verbose_option = 0;
  115.  
  116. /* When a file is recoded over itself, precautions are taken to move the
  117.    timestamps of the original file into the recoded file, so to make the
  118.    recoding the most transparent possible to make, and other tools.
  119.    However, selecting the following option inhibit the timestamps handling,
  120.    thus effectively `touching' the file.  */
  121.  
  122. int touch_option = 0;
  123.  
  124. /* In `texte' charset, some countries use double quotes to mark diaeresis,
  125.    while other countries prefer colons.  The following variable contains the
  126.    diaeresis character for `texte' charset.  Nominally set to a double
  127.    quote, it can be forced to a colon by an option on recode command.  */
  128.  
  129. char diaeresis_char = '"';
  130.  
  131. /* For `latex' charset, it is often convenient to convert the diacritics
  132.    only, while letting other LaTeX code using backslashes unconverted.
  133.    In the other charset, one can edit text as well as LaTeX directives.  */
  134.  
  135. int diacritics_only = 0;
  136.  
  137. /* Tells how various passes will be interconnected.  */
  138.  
  139. enum sequence_strategy
  140. {
  141.   STRATEGY_UNDECIDED,        /* sequencing strategy is undecided yet */
  142.   SEQUENCE_WITH_FILES,        /* do not fork, use intermediate files */
  143.   SEQUENCE_WITH_POPEN,        /* use `popen(3)' to fork processes */
  144.   SEQUENCE_WITH_PIPE        /* fork processes connected with `pipe(2)' */
  145. };
  146. enum sequence_strategy sequence_strategy = STRATEGY_UNDECIDED;
  147.  
  148.  
  149. /* Sequence manipulation routines.  */
  150.  
  151. TYPE_of_step
  152.   *sequence[MAX_CONVERSIONS];    /* sequence of conversions */
  153. int length_of_sequence;        /* length of conversion sequence */
  154.  
  155. /*-------------------------------------------------------------------------.
  156. | Give code index of name into code_keywords.  Returns a negative value if |
  157. | keyword not recognized.                           |
  158. `-------------------------------------------------------------------------*/
  159.  
  160. int
  161. code_index (const char *keyword, int length)
  162. {
  163.   int counter;
  164.  
  165.   /* If keyword not provided, supply the default code if known.  */
  166.  
  167. #ifdef DEFAULT_CODE
  168.   if (length == 0)
  169.     {
  170.       keyword = DEFAULT_CODE;
  171.       length = strlen (DEFAULT_CODE);
  172.     }
  173. #endif /* DEFAULT_CODE */
  174.  
  175.   /* Search for the keyword.  */
  176.  
  177.   for (counter = 0; counter < NUMBER_OF_KEYWORDS; counter++)
  178.     if ((strlen (code_keywords[counter].s) == length
  179.      && strncmp (keyword, code_keywords[counter].s, length) == 0)
  180.     || (strlen (code_keywords[counter].l) == length
  181.         && strncmp (keyword, code_keywords[counter].l, length) == 0))
  182.       return counter;
  183.  
  184.   return -1;
  185. }
  186.  
  187. /*----------------------------------------------------------.
  188. | Find a sequence of single_steps to achieve a conversion.  |
  189. `----------------------------------------------------------*/
  190.  
  191. void
  192. find_sequence (TYPE_code start, TYPE_code goal)
  193. {
  194.   struct 
  195.     {
  196.       TYPE_of_step *step;    /* step who will bring us nearer to goal */
  197.       int cost;            /* cost from here through goal */
  198.     }
  199.   critical_tree[NUMBER_OF_KEYWORDS]; /* critical path tree */
  200.  
  201.   int code;            /* current code */
  202.   TYPE_of_step *step;        /* cursor in possible single_steps */
  203.   int cost;            /* cost under consideration */
  204.   int modified;            /* != 0 if modified since last iteration */
  205.  
  206.   for (code = 0; code < NUMBER_OF_KEYWORDS; code++)
  207.     {
  208.       critical_tree[code].step = NULL;
  209.       critical_tree[code].cost = NOWAY;
  210.     }
  211.   critical_tree[(int) goal].cost = ALREADY;
  212.   modified = 1;
  213.  
  214.   while (modified) 
  215.     {
  216.       modified = 0;
  217.       for (step = single_steps;
  218.        step < single_steps + NUMBER_OF_SINGLE_STEPS;
  219.        step++)
  220.     if ((cost = critical_tree[(int) step->code_after].cost) != NOWAY) 
  221.       {
  222.         cost += step->conversion_cost;
  223.         code = (int) step->code_before;
  224.         if (cost < critical_tree[code].cost) 
  225.           {
  226.         critical_tree[code].step = step;
  227.         critical_tree[code].cost = cost;
  228.         modified = 1;
  229.           }
  230.       }
  231.     }
  232.  
  233.   if (critical_tree[(int) start].cost == NOWAY) 
  234.     {
  235.       fprintf (stderr, "recode: no way to convert from %s to %s.\n",
  236.                code_keywords[(int) start].l, code_keywords[(int) goal].l);
  237.       exit (EXIT_FAILURE);
  238.     }
  239.  
  240.   length_of_sequence = 0;
  241.   for (code = (int) start; code != (int) goal; code = (int) step->code_after)
  242.     {
  243.       step = critical_tree[code].step;
  244.       if (step->routine != NULL)
  245.     if (length_of_sequence < MAX_CONVERSIONS)
  246.       sequence[length_of_sequence++] = step;
  247.     else
  248.       {
  249.         fprintf (stderr, "recode: conversion is too complex.\n");
  250.         exit (EXIT_FAILURE);
  251.       }
  252.     }
  253. }
  254.  
  255. /*-------------------------------------------------------------------.
  256. | Execute the conversion sequence, using several passes with two     |
  257. | alternating intermediate files.  This routine assumes at least one |
  258. | needed recoding step.                             |
  259. `-------------------------------------------------------------------*/
  260.  
  261. void
  262. execute_pass_sequence (const char *input_name, const char *output_name)
  263. {
  264.   int sequence_index;        /* index into sequence */
  265.   char *temp_input_name;    /* step input file name */
  266.   char *temp_output_name;    /* step output file name */
  267. #ifdef MSDOS
  268.   char temp_name_1[13];        /* one temporary file name */
  269.   char temp_name_2[13];        /* another temporary file name */
  270. #endif
  271.   FILE *input_file;        /* input file to recoding step */
  272.   FILE *output_file;        /* output file from recoding step */
  273.   char *exchange_temp;        /* for exchanging temporary names */
  274.  
  275.   /* Choose names for intermediate files.  */
  276.  
  277. #ifdef MSDOS
  278.   strcpy (temp_name_1, "recodex1.tmp");
  279.   strcpy (temp_name_2, "recodex2.tmp");
  280.   temp_input_name = temp_name_1;
  281.   temp_output_name = temp_name_2;
  282. #else
  283.   temp_input_name = tempnam (NULL, "recode.");
  284.   temp_output_name = tempnam (NULL, "recode.");
  285. #endif
  286.  
  287.   /* Execute one pass for each step of the sequence.  */
  288.  
  289.   for (sequence_index = 0;
  290.        sequence_index < length_of_sequence;
  291.        sequence_index++)
  292.     {
  293.  
  294.       /* Select the input file for this step.  */
  295.  
  296.       if (sequence_index == 0)
  297.     if (input_name)
  298.       {
  299.         input_file = fopen (input_name, "r");
  300.         assert (input_file);
  301.       }
  302.     else
  303.       input_file = stdin;
  304.       else
  305.     {
  306.       input_file = fopen (temp_input_name, "r");
  307.       assert (input_file);
  308.     }
  309.  
  310.       /* Select the output file for this step.  */
  311.  
  312.       if (sequence_index == length_of_sequence - 1)
  313.     if (output_name)
  314.       {
  315.         output_file = fopen (output_name, "w");
  316.         assert (output_file);
  317.       }
  318.     else
  319.       output_file = stdout;
  320.       else
  321.     {
  322.       output_file = fopen (temp_output_name, "w");
  323.       assert (output_file);
  324.     }
  325.  
  326.       /* Execute one recoding step.  */
  327.  
  328.       (*sequence[sequence_index]->routine) (input_file, output_file);
  329.  
  330.       /* Close the input file, unlink it if it was temporary.  */
  331.  
  332.       if (sequence_index == 0)
  333.     {
  334.       if (!input_name)
  335.         fclose (input_file);
  336.     }
  337.       else
  338.     {
  339.       fclose (input_file);
  340.       unlink (temp_input_name);
  341.     }
  342.  
  343.       /* Close the output file, exchange names for subsequent step.  */
  344.  
  345.       if (sequence_index == length_of_sequence - 1)
  346.     {
  347.       if (output_name)
  348.         fclose (output_file);
  349.     }
  350.       else
  351.     {
  352.       fclose (output_file);
  353.  
  354.       exchange_temp = temp_input_name;
  355.       temp_input_name = temp_output_name;
  356.       temp_output_name = exchange_temp;
  357.     }
  358.     }
  359.  
  360. #ifndef MSDOS
  361.   free (temp_input_name);
  362.   free (temp_output_name);
  363. #endif
  364. }
  365.  
  366. /*-------------------------------------------------------------------------.
  367. | Execute the conversion sequence, using a chain of invocations of the       |
  368. | program through popen.  This routine assumes that more than one recoding |
  369. | step is needed.                               |
  370. `-------------------------------------------------------------------------*/
  371.  
  372. #ifdef HAVE_POPEN
  373.  
  374. void
  375. execute_popen_sequence (const char *input_name, const char *output_name)
  376. {
  377.   FILE *input_file;        /* input file to recoding step */
  378.   FILE *output_file;        /* output file from recoding step */
  379.   char popen_command[80];    /* to receive command string */
  380.   int status;            /* status to be asserted */
  381.  
  382.   /* Construct a `recode' command for all recoding steps but the first.  */
  383.  
  384.   sprintf (popen_command, "%s -o %s %s:%s %s%s",
  385.        program_name,
  386.        diaeresis_char == ':' ? " -c" : "",
  387.        code_keywords[(int) sequence[1]->code_before].l,
  388.        code_keywords[(int) sequence[length_of_sequence-1]->code_after].l,
  389.        output_name ? "> " : "",
  390.        output_name ? output_name : "");
  391.  
  392.   /* Execute the first recoding step.  */
  393.  
  394.   if (!input_name)
  395.     input_file = stdin;
  396.   else if ((input_file = fopen (input_name, "r")) == NULL)
  397.     {
  398.       perror (input_name);
  399.       exit (EXIT_FAILURE);
  400.     }
  401.  
  402.   if ((output_file = popen (popen_command, "w")) == NULL)
  403.     {
  404.       perror (popen_command);
  405.       exit (EXIT_FAILURE);
  406.     }
  407.  
  408.   (*sequence[0]->routine) (input_file, output_file);
  409.  
  410.   if (input_name)
  411.     fclose (input_file);
  412.   status = pclose (output_file);
  413.   assert (status == 0);
  414. }
  415.  
  416. #endif /* HAVE_POPEN */
  417.  
  418. /*-------------------------------------------------------------------------.
  419. | Execute the conversion sequence, forking the program many times for all  |
  420. | elementary steps, interconnecting them with pipes.  This routine assumes |
  421. | at least one recoding step is needed.                       |
  422. `-------------------------------------------------------------------------*/
  423.  
  424. #ifndef HAVE_DUP2
  425. #undef HAVE_PIPE
  426. #endif
  427.  
  428. #ifdef HAVE_PIPE
  429.  
  430. void
  431. execute_pipe_sequence (const char *input_name, const char *output_name)
  432. {
  433.   int sequence_index;        /* index into sequence */
  434.   TYPE_of_step *step;        /* pointer into single_steps */
  435.  
  436.   FILE *input_file;        /* input file to recoding step */
  437.   FILE *output_file;        /* output file from recoding step */
  438.   int pipe_pair[2];        /* pair of file descriptors for a pipe */
  439.   int child_process;        /* child process number, zero if child */
  440.   int status;            /* status to be asserted */
  441.  
  442.   /* Prepare the final output file.  */
  443.  
  444.   if (output_name)
  445.     {
  446.       output_file = fopen (output_name, "w");
  447.       assert (output_file);
  448.     }
  449.   else
  450.     output_file = stdout;
  451.  
  452.   /* Create all subprocesses and interconnect them.  */
  453.  
  454.   for (sequence_index = length_of_sequence - 1;
  455.        sequence_index > 0;
  456.        sequence_index--)
  457.     {
  458.       status = pipe (pipe_pair);
  459.       assert (status == 0);
  460.       child_process = fork ();
  461.       assert (child_process >= 0);
  462.       if (child_process == 0)
  463.     {
  464.  
  465.           /* The child executes its recoding step, reading from the pipe
  466.              and writing to the current output file; then it exits.  */
  467.  
  468.       status = close (pipe_pair[1]);
  469.       assert (status == 0);
  470.       input_file = fdopen (pipe_pair[0], "r");
  471.       assert (input_file);
  472.  
  473.       (*sequence[sequence_index]->routine) (input_file, output_file);
  474.  
  475.       fclose (input_file);
  476.       if (sequence_index < length_of_sequence - 1 || output_name)
  477.         fclose (output_file);
  478.       exit (EXIT_SUCCESS);
  479.     }
  480.       else
  481.     {
  482.  
  483.           /* The parent redirects the current output file to the pipe.  */
  484.  
  485.       status = dup2 (pipe_pair[1], fileno (output_file));
  486.       assert (status != -1);
  487.       status = close (pipe_pair[0]);
  488.       assert (status == 0);
  489.       status = close (pipe_pair[1]);
  490.       assert (status == 0);
  491.     }
  492.     }
  493.   
  494.   /* All the children are created, blocked on read.  Now, feed the whole
  495.      chain of processes with the output of the first recoding step.  */
  496.  
  497.   if (!input_name)
  498.     input_file = stdin;
  499.   else
  500.     {
  501.       input_file = fopen (input_name, "r");
  502.       assert (input_file);
  503.     }
  504.  
  505.   (*sequence[0]->routine) (input_file, output_file);
  506.  
  507.   if (input_name)
  508.     fclose (input_file);
  509.   if (output_name)
  510.     fclose (output_file);
  511.  
  512.   /* Wait on all children, mainly to avoid synchronisation problems on
  513.      output file contents, but also to reduce the number of zombie
  514.      processes in case the user recodes many files at once.  */
  515.  
  516.   while (wait (NULL) > 0)
  517.     ;
  518. }
  519.  
  520. #endif /* HAVE_PIPE */
  521.  
  522. /*-----------------------------------------------------------------------.
  523. | Execute the conversion sequence, using the selected strategy whenever     |
  524. | more than one conversion step is needed.  If no conversion are needed, |
  525. | merely copy the input onto the output.                 |
  526. `-----------------------------------------------------------------------*/
  527.  
  528. /* If some sequencing strategies are missing, this routine automatically
  529.    uses fallback strategies.  */
  530.  
  531. void
  532. execute_sequence (const char *input_name, const char *output_name)
  533. {
  534.   FILE *input_file;        /* input file to recoding step */
  535.   FILE *output_file;        /* output file from recoding step */
  536.   int character;        /* the whole file will go through */
  537.  
  538. #ifdef MSDOS
  539.   if (!input_name)
  540.     setmode (fileno (stdin), O_BINARY);
  541.   if (!output_name)
  542.     setmode (fileno (stdout), O_BINARY);
  543.   _fmode = O_BINARY;
  544. #endif
  545.  
  546.   if (verbose_option && input_name)
  547.     {
  548.       fprintf (stderr, "Recoding %s...", input_name);
  549.       fflush (stderr);
  550.     }
  551.  
  552.   if (length_of_sequence > 1)
  553.     switch (sequence_strategy)
  554.       {
  555.       case STRATEGY_UNDECIDED:
  556.     assert (0);
  557.  
  558.       case SEQUENCE_WITH_PIPE:
  559. #ifdef HAVE_PIPE
  560.     execute_pipe_sequence (input_name, output_name);
  561.     break;
  562. #endif
  563.  
  564.       case SEQUENCE_WITH_POPEN:
  565. #ifdef HAVE_POPEN
  566.     execute_popen_sequence (input_name, output_name);
  567.     break;
  568. #endif
  569.  
  570.       case SEQUENCE_WITH_FILES:
  571.     execute_pass_sequence (input_name, output_name);
  572.     break;
  573.       }
  574.   else
  575.     {
  576.  
  577.       /* This is a single-step recoding or a mere copy.  Do it.  */
  578.  
  579.       if (!input_name)
  580.     input_file = stdin;
  581.       else if ((input_file = fopen (input_name, "r")) == NULL)
  582.     {
  583.       perror (input_name);
  584.       exit (EXIT_FAILURE);
  585.     }
  586.  
  587.       if (!output_name)
  588.     output_file = stdout;
  589.       else if ((output_file = fopen (output_name, "w")) == NULL)
  590.     {
  591.       perror (output_name);
  592.       exit (EXIT_FAILURE);
  593.     }
  594.  
  595.       if (length_of_sequence == 1)
  596.     (*sequence[0]->routine) (input_file, output_file);
  597.       else
  598.     while ((character = getc (input_file)) != EOF)
  599.       putc (character, output_file);
  600.  
  601.       if (input_name)
  602.     fclose (input_file);
  603.       if (output_name)
  604.     fclose (output_file);
  605.     }
  606.  
  607.   if (verbose_option && input_name)
  608.     {
  609.       fprintf (stderr, " done\n");
  610.       fflush (stderr);
  611.     }
  612. }
  613.  
  614.  
  615. /* Main program.  */
  616.  
  617. void
  618. echo_sequence (void)
  619. {
  620.   int sequence_index;        /* index into sequence */
  621.   TYPE_of_step *step;        /* pointer into single_steps */
  622.  
  623.   if (length_of_sequence == 0)
  624.     fprintf (stderr, "Recoding by mere copying\n");
  625.   else
  626.     {
  627.       fprintf (stderr, "Recoding through ");
  628.       for (sequence_index = 0;
  629.        sequence_index < length_of_sequence;
  630.        sequence_index++)
  631.     {
  632.       step = sequence[sequence_index];
  633.       fprintf (stderr, "%s%s:%s",
  634.            sequence_index == 0 ? "" : " | ",
  635.            code_keywords[(int) step->code_before].l,
  636.            code_keywords[(int) step->code_after].l);
  637.     }
  638.       fprintf (stderr, "\n");
  639.     }
  640. }
  641.  
  642. void
  643. usage_and_exit (void)
  644. {
  645.   int keyword_index;
  646.  
  647.   print_version ();
  648.  
  649.   fprintf (stderr, "\
  650. usage: recode [OPTION]... [BEFORE]:[AFTER] [FILE]...\n\
  651.   -C    display Copyright and copying conditions, then exit\n\
  652.   -c    use colons instead of double quotes for diaeresis\n\
  653.   -d    limit conversion to diacritics or alike for LaTeX\n\
  654.   -f    force file replacement even if non reversible\n\
  655.   -i    use intermediate files for sequencing passes\n\
  656. ");
  657. #ifdef HAVE_POPEN
  658.   fprintf (stderr, "\
  659.   -o    use popen machinery for sequencing passes\n\
  660. ");
  661. #else
  662.   fprintf (stderr, "\
  663.   -o    interpreted as -i on this system\n\
  664. ");
  665. #endif
  666. #ifdef HAVE_PIPE
  667.   fprintf (stderr, "\
  668.   -p    use pipe machinery for sequencing passes\n\
  669. ");
  670. #else
  671.   fprintf (stderr, "\
  672.   -p    interpreted as -o on this system\n\
  673. ");
  674. #endif
  675.   fprintf (stderr, "\
  676.   -t    touch the recoded files after replacement\n\
  677.   -v    be verbose, tell elementary steps sequence\n\
  678. ");
  679.  
  680.   for (keyword_index = 0;
  681.        keyword_index < NUMBER_OF_KEYWORDS;
  682.        keyword_index++)
  683.     {
  684.       if (keyword_index % 4 == 0)
  685.     fprintf (stderr, keyword_index == 0 ? "  CODE" : "\n");
  686.       fprintf (stderr, "\t%s %s",
  687.            code_keywords[keyword_index].s,
  688.            code_keywords[keyword_index].l);
  689.     }
  690.   fprintf (stderr, "\n");
  691.  
  692.   fprintf (stderr, "\
  693. Each FILE is recoded over itself, destroying the original.  If no\n\
  694. FILE is specified, then act as a filter and recode stdin to stdout.\n\
  695. If none of -i, -o and -p are given, presume -p if no FILE, else -i.\n\
  696. Beware that option `-f' is always selected, even if not given.\n\
  697. \n");
  698.  
  699.   exit (EXIT_FAILURE);
  700. }
  701.  
  702. int
  703. main (int argc, const char *argv[])
  704. {
  705.   extern int optind;        /* index of argument */
  706.   int option_char;        /* option character */
  707.   int start_index;        /* index of starting code */
  708.   int end_index;        /* index of end code */
  709.   const char *input_name;    /* input file name */
  710.   char output_name[200];    /* output file name */
  711.   FILE *file;            /* file to check or stat */
  712.   char *cursor;            /* all purpose cursor */
  713. #ifdef MSDOS
  714.   struct ftime stamp_stat;    /* input file time stamps */
  715. #else
  716.   struct stat stamp_stat;    /* input file time stamps */
  717.   time_t stamp_utime[2];    /* recoded file time stamps */
  718. #endif
  719.  
  720.   /* Decode command options.  */
  721.  
  722.   program_name = argv[0];
  723.  
  724.   while ((option_char = getopt (argc, argv, "Ccdfioptv")) != EOF)
  725.     switch (option_char)
  726.       {
  727.       case 'C':
  728.     print_version ();
  729.     print_copyright ();
  730.     exit (EXIT_SUCCESS);
  731.  
  732.       case 'c':
  733.     diaeresis_char = ':';
  734.     break;
  735.  
  736.       case 'd':
  737.     diacritics_only = 1;
  738.     break;
  739.  
  740.       case 'f':
  741.     force_option = 1;
  742.     break;
  743.  
  744.       case 'i':
  745.     sequence_strategy = SEQUENCE_WITH_FILES;
  746.     break;
  747.  
  748.       case 'o':
  749.     sequence_strategy = SEQUENCE_WITH_POPEN;
  750.     break;
  751.  
  752.       case 'p':
  753.     sequence_strategy = SEQUENCE_WITH_PIPE;
  754.     break;
  755.  
  756.       case 't':
  757.     touch_option = 1;
  758.     break;
  759.  
  760.       case 'v':
  761.     verbose_option = 1;
  762.     break;
  763.  
  764.       default:
  765.     usage_and_exit ();
  766.       }
  767.  
  768.   /* Decode the BEFORE:AFTER argument.  */
  769.  
  770.   if (optind+1 > argc)
  771.     usage_and_exit ();
  772.  
  773.   cursor = strchr (argv[optind], ':');
  774.   if (!cursor)
  775.     usage_and_exit ();
  776.   start_index = code_index (argv[optind], cursor - argv[optind]);
  777.   if (start_index < 0)
  778.     usage_and_exit ();
  779.   cursor++;
  780.   end_index = code_index (cursor, strlen(cursor));
  781.   if (end_index < 0)
  782.     usage_and_exit ();
  783.   optind++;
  784.  
  785.   /* Establish the sequence of recoding steps.  */
  786.  
  787.   length_of_sequence = 0;
  788.   find_sequence (start_index, end_index);
  789.  
  790.   if (verbose_option)
  791.     echo_sequence ();
  792.  
  793.   /* If there is no input file, act as a filter.  Else, recode all files
  794.      over themselves.  */
  795.  
  796.   if (optind < argc)
  797.     {
  798.  
  799.       /* When reading and writing files, unless the user selected otherwise,
  800.      avoid forking and use intermediate files.  */
  801.  
  802.       if (sequence_strategy == STRATEGY_UNDECIDED)
  803.     sequence_strategy = SEQUENCE_WITH_FILES;
  804.  
  805.       /* In case files are recoded over themselves and there is no
  806.          recoding step at all, do not even try to touch the files.  */
  807.  
  808.       if (length_of_sequence > 0)
  809.  
  810.     /* Process files, one at a time.  */
  811.  
  812.     for (; optind < argc; optind++)
  813.       {
  814.         input_name = argv[optind];
  815.  
  816.         /* Check if the file can be read and rewritten.  */
  817.  
  818.         if ((file = fopen (input_name, "r+")) == NULL)
  819.           {
  820.         perror (input_name);
  821.         exit (EXIT_FAILURE);
  822.           }
  823.  
  824.         /* Save the input file time stamp.  */
  825.  
  826.         if (!touch_option)
  827.           {
  828. #ifdef MSDOS
  829.         getftime (fileno (file), &stamp_stat);
  830. #else
  831.         fstat (fileno (file), &stamp_stat);
  832. #endif
  833.           }
  834.  
  835.         fclose (file);
  836.         
  837.         /* Choose an output file in the same directory.  */
  838.  
  839.         strcpy (output_name, input_name);
  840.         for (cursor = output_name + strlen (output_name);
  841.          cursor > output_name && cursor[-1] != '/'
  842. #ifdef MSDOS
  843.          && cursor[-1] != '\\' && cursor[-1] != ':'
  844. #endif
  845.          ; cursor--)
  846.           ;
  847.         strcpy (cursor, "recodeXX.TMP");
  848.  
  849.         /* Recode the file.  */
  850.  
  851.         execute_sequence (input_name, output_name);
  852.  
  853.         /* Move the new file over the original.  */
  854.  
  855.         unlink (input_name);
  856. #ifdef HAVE_RENAME
  857.         rename (output_name, input_name);
  858. #else
  859.         link (output_name, input_name);
  860.         unlink (output_name);
  861. #endif
  862.  
  863.         /* Adjust the time stamp for the new file.  */
  864.  
  865.         if (!touch_option)
  866.           {
  867. #ifdef MSDOS
  868.         file = fopen (input_name, "r");
  869.         assert (file);
  870.         setftime (fileno (file), &stamp_stat);
  871.         fclose (file);
  872. #else
  873.         stamp_utime[0] = stamp_stat.st_atime;
  874.         stamp_utime[1] = stamp_stat.st_mtime;
  875.         utime (input_name, stamp_utime);
  876. #endif
  877.           }
  878.       }
  879.     }
  880.   else
  881.     {
  882.  
  883.       /* When reading stdin and writing stdout, unless the user selected
  884.          otherwise, fork processes interconnected with pipes.  */
  885.  
  886.       if (sequence_strategy == STRATEGY_UNDECIDED)
  887.     sequence_strategy = SEQUENCE_WITH_PIPE;
  888.  
  889.       execute_sequence (NULL, NULL);
  890.     }
  891.   exit (EXIT_SUCCESS);
  892. }
  893.